package com.tengfei.main.course.service.impl;

import com.tengfei.common.utils.DateUtils;
import com.tengfei.common.utils.file.FileUtils;
import com.tengfei.main.course.config.AliyunConfig;
import com.tengfei.main.course.config.FfmPegConfig;
import com.tengfei.main.course.domain.Video;
import com.tengfei.main.course.mapper.VideoMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;

import javax.annotation.Resource;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author feige
 * @version v1.0
 * @date 2022-08-12-15:12
 * @description 通过cmd命令执行ffmpeg
 */
@Slf4j
@Component
public class FfmpegExec {

    @Resource
    private FfmPegConfig ffmPegConfig;
    @Resource
    private AliyunConfig aliyunConfig;
    @Resource
    private OssServiceImpl ossService;
    @Resource
    private VideoMapper videoMapper;

    DefaultFFMPEGLocator defaultFFMPEGLocator = new DefaultFFMPEGLocator();
    String ffmpegPath = defaultFFMPEGLocator.getExecutablePath();


    /**
     * @param inVideoPath 视频输入地址
     * @param outPath     视频输出根路径
     * @param outName     视频输出文件名称
     * @param videoId     视频id
     */
    @Async
    public void video2m3u8(String inVideoPath, String outPath, String outName, Long videoId) {
        long start = System.currentTimeMillis();
        // 目录不存在，创建目录
        File file = new File(ffmPegConfig.getOutSrc() + outPath);
        if (!file.exists()) {
            file.mkdirs();
        }
        file = null;
        String outVideoPath = new StringBuilder(ffmPegConfig.getOutSrc())
                .append(outPath)
                .append(outName)
                .append(".m3u8")
                .toString();

        String outCoverPath = new StringBuilder(ffmPegConfig.getOutSrc())
                .append(outPath)
                .append(outName)
                .append(".png")
                .toString();
        log.info("视频开始分片切割....");
        int res = execute(inVideoPath, outVideoPath);
        int duration = getVideoInfo(inVideoPath, outCoverPath);
        log.info("视频分片切割结束");
        log.info("视频分割结果：{}", res == 0 ? "成功" : "失败");
        long end = System.currentTimeMillis();
        log.info("解析时长：{}", (end - start));

        // 上传信息到oss
        // 上传视频
        File m3u8file = new File(ffmPegConfig.getOutSrc() + outPath);
        File[] files = m3u8file.listFiles();
        for (File f : files) {
            if (!f.isDirectory()) {
                String videoPath = new StringBuilder(aliyunConfig.getRootPath())
                        .append("/video/")
                        .append(outPath)
                        .append(f.getName())
                        .toString();
                // 上传文件到oss
                ossService.uploadVideo(videoPath, f);
            }
        }
        // 删除temp中的视频
        FileUtils.delDirectory(new File(ffmPegConfig.getTemp() + outName));
        // 上传封面
        File coverImg = new File(new StringBuilder(ffmPegConfig.getOutSrc())
                .append(outPath)
                .append(outName)
                .append(".png")
                .toString());
        String filePath = new StringBuilder(aliyunConfig.getRootPath())
                .append("/resources/")
                .append(DateUtils.datePath())
                .append("/")
                .append(outName)
                .append(".png")
                .toString();
        try (FileInputStream fileInputStream = new FileInputStream(coverImg)) {
            ossService.uploadImg(filePath, fileInputStream);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        // 更新数据库
        Video video = new Video();
        video.setVideoId(videoId);
        video.setVideoCover(aliyunConfig.getUrlPrefix() + filePath);
        video.setVideoDuration(duration);
        video.setVideoStatus(1);
        videoMapper.updateById(video);
    }

    /**
     * 命令详解：
     * ffmpeg -i xxx/xxx.mp4 -c:v libx264 -c:a aac -hls_time 10 -hls_list_size 0 -strict -2 -s 1920x1080 -f hls -threads 10 -preset ultrafast xxx/xxx.m3u8
     * 1. ffmpeg 指ffmpeg应用的路径
     * 2. -i 接需要编码的视频地址
     * 3. -c:v libx264 -c:a aac 指视频按h.254编码，音频按aac编码
     * 4. -hls_time 10 表示分片大小，既视频按10秒段进行切分
     * 5. -hls_list_size 0 -strict -2 表示视频按10秒每段切分，为了控制大小，会发生2秒左右的偏移
     * 6. -s 1920x1080 输出视频的分辨率
     * 7. -f hls 视频输出格式是hls
     * 8. -threads 10 -preset ultrafast 开启多线程编码，10个线程，并非越多越好，10个左右刚好合适
     * 9. xxx/xxx.m3u8 输出视频的位置，格式m3u8
     */

    /**
     * 执行命令
     *
     * @param inVideoPath  输入视频的地址
     * @param outVideoPath 输出视频地址
     */
    private int execute(String inVideoPath, String outVideoPath) {
        log.info("ffmepg位置：{}", ffmpegPath);
        // ffmpeg程序位置
        String cmd = new StringBuilder(ffmpegPath)
                .append(" -i ")
                // 输入视频位置
                .append(inVideoPath)
                .append(" -c:v libx264 -c:a aac -hls_time ")
                // 分片大小
                .append(ffmPegConfig.getSize())
                .append(" -hls_list_size 0 -strict -2 -s 1920x1080 -f hls -threads ")
                // 线程数
                .append(ffmPegConfig.getThreads())
                .append(" -preset ultrafast ")
                // 输出位置
                .append(outVideoPath)
                .toString();

        Runtime runtime = Runtime.getRuntime();
        Process ffmpeg = null;
        InputStream errorIs = null;
        try {
            ffmpeg = runtime.exec(cmd);
            // 开启日志
            errorIs = ffmpeg.getErrorStream();
            // 输入指令y，表示同意跳过执行
            OutputStream os = ffmpeg.getOutputStream();
            os.write("y".getBytes("UTF-8"));
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        int res = close(ffmpeg, errorIs, false);

        return res;
    }

    /**
     * 命令详解：
     * ffmpeg -i xxx/xxx.mp4 -ss 00:00:01 -frames:v 1 xxx/xxx.png
     * 1. ffmpeg 指ffmpeg应用的路径
     * 2. -i 接需要编码的视频地址
     * 3. -ss 00:00:01 -frames:v 1 获取视频第一秒的第一帧为视频封面
     * 4. xxx/xxx.png 封面图片输出地址
     */
    /**
     * 获取视频封面与时长
     *
     * @param inVideoPath
     * @param outCoverPath
     * @return duration 0:执行失败
     */
    private int getVideoInfo(String inVideoPath, String outCoverPath) {
        String cmd = new StringBuilder(ffmpegPath)
                .append(" -i ")
                // 输入视频位置
                .append(inVideoPath)
                // 获取视频第一帧为封面
                .append(" -ss 00:00:01 -frames:v 1 ")
                .append(outCoverPath)
                .toString();

        Runtime runtime = Runtime.getRuntime();
        Process ffmpeg = null;
        InputStream errorIs = null;
        try {
            ffmpeg = runtime.exec(cmd);
            // 开启日志
            errorIs = ffmpeg.getErrorStream();
            // 输入指令y，表示同意跳过执行
            OutputStream os = ffmpeg.getOutputStream();
            os.write("y".getBytes("UTF-8"));
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        int duration = close(ffmpeg, errorIs, true);

        return duration;
    }

    /**
     * 打印过程并关闭
     *
     * @param ffmpeg
     * @param errorIs
     * @return duration
     */
    private int close(Process ffmpeg, InputStream errorIs, boolean printLog) {
        //打印过程
        StringBuilder info = new StringBuilder();
        try {
            int len = 0;
            while (true) {
                if (!((len = errorIs.read()) != -1)) break;
                if (printLog) {
                    info.append((char) len);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 正则匹配时长信息
        int duration = 0;
        if (printLog) {
            Matcher matcher = Pattern.compile("(?i)duration:\\s*([0-9\\:\\.]+)")
                    .matcher(info.toString());
            while (matcher.find()) {
                String group = matcher.group(1);
                duration = getSeconds4Str(group);
                break;
            }
            log.info("时长(s)：{}", duration);
        }

        info = null;
        if (errorIs != null) {
            try {
                errorIs.close();
            } catch (Throwable t) {
                log.warn("关闭输入流失败", t);
            }
        }
        // 确保命令执行完毕
        try {
            ffmpeg.waitFor();
        } catch (InterruptedException ex) {
            log.error("在等待过程中强制关闭：{}", ex);
        }
        int res = ffmpeg.exitValue();
        if (res != 0) {
            duration = 0;
        }

        if (ffmpeg != null) {
            ffmpeg.destroy();
            ffmpeg = null;
        }
        return duration;
    }

    /**
     * 字符格式化为秒
     *
     * @param durationStr
     * @return
     */
    private int getSeconds4Str(String durationStr) {
        int duration = 0;
        if (null != durationStr && durationStr.length() > 0) {
            String[] durationStrArr = durationStr.split("\\:");
            String hour = durationStrArr[0];
            String minute = durationStrArr[1];
            //特殊
            String second = "";
            String secondTmp = durationStrArr[2];
            if (secondTmp.contains(".")) {
                String[] seconedTmpArr = secondTmp.split("\\.");
                second = seconedTmpArr[0];
            } else {
                second = secondTmp;
            }
            try {
                duration = Integer.parseInt(hour) * 3600 + Integer.parseInt(minute) * 60 + Integer.parseInt(second);
            } catch (Exception e) {
                return 0;
            }
        }
        return duration;
    }


}
